package com.querydsl.example.dao;

import com.querydsl.core.dml.StoreClause;
import com.querydsl.core.group.GroupBy;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.QBean;
import com.querydsl.example.dto.Product;
import com.querydsl.example.dto.ProductL10n;
import com.querydsl.example.dto.Supplier;
import com.querydsl.sql.SQLQueryFactory;
import com.querydsl.sql.dml.SQLInsertClause;
import org.springframework.transaction.annotation.Transactional;

import javax.inject.Inject;
import java.util.List;

import static com.querydsl.core.types.Projections.bean;
import static com.querydsl.example.sql.QProduct.product;
import static com.querydsl.example.sql.QProductL10n.productL10n;
import static com.querydsl.example.sql.QSupplier.supplier;

@Transactional
public class ProductDaoImpl implements ProductDao {

    @Inject
    SQLQueryFactory queryFactory;

    final QBean<ProductL10n> productL10nBean = bean(ProductL10n.class,
            productL10n.description, productL10n.lang, productL10n.name);

    final QBean<Product> productBean = bean(Product.class,
            product.id, product.name, product.otherProductDetails, product.price,
            bean(Supplier.class, supplier.all()).as("supplier"),
            GroupBy.set(productL10nBean).as("localizations"));

    @Override
    public Product findById(long id) {
        List<Product> persons = findAll(product.id.eq(id));
        return persons.isEmpty() ? null : persons.get(0);
    }

    @Override
    public List<Product> findAll(Predicate... where) {
        return queryFactory.from(product)
            .innerJoin(product.supplierFk, supplier)
            .innerJoin(product._productFk, productL10n)
            .where(where)
            .transform(GroupBy.groupBy(product.id).list(productBean));
    }

    private <T extends StoreClause<T>> T populate(T dml, Product p) {
        return dml.set(product.name, p.getName())
            .set(product.otherProductDetails, p.getOtherProductDetails())
            .set(product.price, p.getPrice())
            .set(product.supplierId, p.getSupplier().getId());
    }

    @Override
    public Product save(Product p) {
        Long id = p.getId();

        if (id == null) {
            id = populate(queryFactory.insert(product), p)
                .executeWithKey(product.id);
            p.setId(id);
        } else {
            populate(queryFactory.update(product), p)
                .where(product.id.eq(id))
                .execute();

            // delete l10n rows
            queryFactory.delete(productL10n)
                .where(productL10n.productId.eq(id))
                .execute();
        }

        SQLInsertClause insert = queryFactory.insert(productL10n);
        for (ProductL10n l10n : p.getLocalizations()) {
            insert.set(productL10n.productId, id)
                .set(productL10n.description, l10n.getDescription())
                .set(productL10n.lang, l10n.getLang())
                .set(productL10n.name, l10n.getName())
                .addBatch();
        }
        insert.execute();

        return p;
    }

    @Override
    public long count() {
        return queryFactory.from(product).fetchCount();
    }

    @Override
    public void delete(Product p) {
        // TODO use combined delete clause
        queryFactory.delete(productL10n)
            .where(productL10n.productId.eq(p.getId()))
            .execute();

        queryFactory.delete(product)
            .where(product.id.eq(p.getId()))
            .execute();
    }

}
